package main

import (
	"context"
	"errors"
	"hedge/persistence"
	"strconv"

	"gitee.com/hacker61/go-binance/v2"
	"gitee.com/hacker61/go-binance/v2/futures"
)

var (
	symbolExchangeInfos map[string]persistence.SymbolExchangeInfo
)

func order_init() {
	if ppersistence == nil {
		ppersistence = persistence.PersistenceMysql{}.Init(conf.MysqlOpt.Host, conf.MysqlOpt.Port, conf.MysqlOpt.User, conf.MysqlOpt.Passwd, conf.MysqlOpt.DBName)
	}
	var err error
	symbolExchangeInfos, err = ppersistence.QueryAllSymbolExchangeInfo()
	if err != nil {
		Error.Println(err)
		panic(err)
	}
}

// 创建现货多单
func createBinanceBuyOrder(symbol string, quoteQty float64) (cummulativeQuoteQty, executedQty, executedPrice float64, err error) {
	createOrderService := binanceClient.NewCreateOrderService().
		Symbol(symbol).
		Side(binance.SideTypeBuy).
		Type(binance.OrderTypeMarket).
		QuoteOrderQty(strconv.FormatFloat(quoteQty, 'f', 2, 64))
	order, err := createOrderService.Do(context.Background())
	if err != nil {
		Error.Printf("%v\n", err)
		return
	}

	Info.Println(order)
	Info.Println(order.CummulativeQuoteQuantity)
	for _, fill := range order.Fills {
		Info.Println(fill)
		fillquantity, _ := strconv.ParseFloat(fill.Quantity, 64)
		fillprice, _ := strconv.ParseFloat(fill.Price, 64)
		cummulativeQuoteQty += fillquantity * fillprice
		executedQty += fillquantity
	}
	executedPrice = cummulativeQuoteQty / executedQty
	Info.Printf("client Order : %v, %v, %v\n", cummulativeQuoteQty, executedQty, executedPrice)

	return
}

// 平掉现货多单
func createBinanceSellOrder(symbol string, quantity float64) (err error) {
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.SpotLotPrecision
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	createOrderService := binanceClient.NewCreateOrderService().
		Symbol(symbol).
		Side(binance.SideTypeSell).
		Type(binance.OrderTypeMarket).
		Quantity(sQuantity)
	_, err = createOrderService.Do(context.Background())
	if err != nil {
		Error.Printf("%v\n", err)
		return
	}
	return
}

func createBinanceBuyLimitOrder(symbol, sPrice string, quoteQty float64) (orderId int64, err error) {
	price, _ := strconv.ParseFloat(sPrice, 64)
	quantity := quoteQty / price
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.SpotLotPrecision
	if exchangeInfo.FuturesLotPrecision < exchangeInfo.SpotLotPrecision {
		quantityPrecision = exchangeInfo.FuturesLotPrecision
	}
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	orderId, err = createBinanceBuyLimitOrder_v2(symbol, sPrice, sQuantity)
	return
}

func createBinanceBuyLimitOrder_v2(symbol, price, quantity string) (orderId int64, err error) {
	Info.Printf("Binance limitBuyOrder symbol = %s, price = %s, quantity = %s\n", symbol, price, quantity)
	createOrderService := binanceClient.NewCreateOrderService().
		Symbol(symbol).
		Side(binance.SideTypeBuy).
		Type(binance.OrderTypeLimit).
		Price(price).
		TimeInForce(binance.TimeInForceTypeGTC).
		Quantity(quantity)
	var order *binance.CreateOrderResponse
	order, err = createOrderService.Do(context.Background())

	if err == nil {
		orderId = order.OrderID
	}

	return
}

func createBinanceSellLimitOrder(symbol, sPrice string, quantity float64) (orderId int64, err error) {
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.SpotLotPrecision
	//if exchangeInfo.FuturesLotPrecision < exchangeInfo.SpotLotPrecision {
	//	quantityPrecision = exchangeInfo.FuturesLotPrecision
	//}
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	Info.Printf("Binance Quantity = %s\n", sQuantity)
	orderId, err = createBinanceSellLimitOrder_v2(symbol, sPrice, sQuantity)

	return
}

func createBinanceSellLimitOrder_v2(symbol, sPrice, sQuantity string) (orderId int64, err error) {
	Info.Printf("Binance limitSellOrder symbol = %s, price = %s, quantity = %s\n", symbol, sPrice, sQuantity)
	createOrderService := binanceClient.NewCreateOrderService().
		Symbol(symbol).
		Side(binance.SideTypeSell).
		Type(binance.OrderTypeLimit).
		Price(sPrice).
		TimeInForce(binance.TimeInForceTypeGTC).
		Quantity(sQuantity)
	var order *binance.CreateOrderResponse
	order, err = createOrderService.Do(context.Background())

	if err == nil {
		orderId = order.OrderID
	}

	return
}

// 创建合约空单
func createFuturesSellOrder(symbol string, futuresPrice, quoteQty float64) (futuresCummuQuoteQty, futuresExecQty, futuresExecPrice float64, err error) {
	var quantity float64
	quantity = quoteQty / futuresPrice
	futuresCummuQuoteQty, futuresExecQty, futuresExecPrice, err = createFuturesSellOrder_v2(symbol, quantity)

	return
}

func createFuturesSellOrder_v2(symbol string, quantity float64) (futuresCummuQuoteQty, futuresExecQty, futuresExecPrice float64, err error) {
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol futures precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.FuturesLotPrecision
	if exchangeInfo.SpotLotPrecision < exchangeInfo.FuturesLotPrecision {
		quantityPrecision = exchangeInfo.SpotLotPrecision
	}
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	Info.Printf("Futures Quantity = %s\n", sQuantity)
	createOrderService := futuresClient.NewCreateOrderService().
		Symbol(symbol).
		Side(futures.SideTypeSell).
		Type(futures.OrderTypeMarket).
		Quantity(sQuantity)
	order, err := createOrderService.Do(context.Background())
	if err != nil {
		Error.Printf("%v\n", err)
		return
	}

	Info.Println(order)
	futuresCummuQuoteQty, futuresExecQty, futuresExecPrice, _, err = getFuturesOrderInf(symbol, order.OrderID)
	//futuresCummuQuoteQty, _ = strconv.ParseFloat(order.CumQuote, 64)
	//futuresExecQty, _ = strconv.ParseFloat(order.ExecutedQuantity, 64)
	//futuresExecPrice = futuresCummuQuoteQty / futuresExecQty
	Info.Printf("futures Order: %v, %v, %v\n", futuresCummuQuoteQty, futuresExecQty, futuresExecPrice)

	return
}

// 平掉合约空单
func createFuturesBuyOrder(symbol string, quantity float64) (err error) {
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol futures precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.FuturesLotPrecision
	//if exchangeInfo.SpotLotPrecision < exchangeInfo.FuturesLotPrecision {
	//	quantityPrecision = exchangeInfo.SpotLotPrecision
	//}
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	createOrderService := futuresClient.NewCreateOrderService().
		Symbol(symbol).
		Side(futures.SideTypeBuy).
		Type(futures.OrderTypeMarket).
		ReduceOnly(true).
		Quantity(sQuantity)
	_, err = createOrderService.Do(context.Background())
	if err != nil {
		Error.Printf("%v\n", err)
		return
	}

	return
}

func createFuturesSellLimitOrder(symbol, sFuturesPrice string, quoteQty float64) (orderId int64, err error) {
	futuresPrice, _ := strconv.ParseFloat(sFuturesPrice, 64)
	var quantity float64
	quantity = quoteQty / futuresPrice
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol futures precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.FuturesLotPrecision
	if exchangeInfo.SpotLotPrecision < exchangeInfo.FuturesLotPrecision {
		quantityPrecision = exchangeInfo.SpotLotPrecision
	}
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	orderId, err = createFuturesSellLimitOrder_v2(symbol, sFuturesPrice, sQuantity)

	return
}

func createFuturesSellLimitOrder_v2(symbol, sFuturesPrice, sQuantity string) (orderId int64, err error) {
	Info.Printf("Futures limitSellOrder symbol = %s, futuresPrice = %s, Quantity = %s\n", symbol, sFuturesPrice, sQuantity)
	createOrderService := futuresClient.NewCreateOrderService().
		Symbol(symbol).
		Side(futures.SideTypeSell).
		Type(futures.OrderTypeLimit).
		Price(sFuturesPrice).
		TimeInForce(futures.TimeInForceTypeGTC).
		Quantity(sQuantity)
	var order *futures.CreateOrderResponse
	order, err = createOrderService.Do(context.Background())

	if err == nil {
		orderId = order.OrderID
	}

	return
}

func createFuturesBuyLimitOrder(symbol, sFuturesPrice string, quantity float64) (orderId int64, err error) {
	var quantityPrecision int
	exchangeInfo, bOK := symbolExchangeInfos[symbol]
	if !bOK {
		err = errors.New("symbol futures precision unknown")
		Warning.Println(err)
		return
	}
	quantityPrecision = exchangeInfo.FuturesLotPrecision
	//if exchangeInfo.SpotLotPrecision < exchangeInfo.FuturesLotPrecision {
	//	quantityPrecision = exchangeInfo.SpotLotPrecision
	//}
	sQuantity := strconv.FormatFloat(quantity, 'f', quantityPrecision, 64)
	orderId, err = createFuturesBuyLimitOrder_v2(symbol, sFuturesPrice, sQuantity)

	return
}

func createFuturesBuyLimitOrder_v2(symbol, sFuturesPrice, sQuantity string) (orderId int64, err error) {
	Info.Printf("Futures limitBuyOrder symbol = %s, futuresPrice = %s, Quantity = %s\n", symbol, sFuturesPrice, sQuantity)
	createOrderService := futuresClient.NewCreateOrderService().
		Symbol(symbol).
		Side(futures.SideTypeBuy).
		Type(futures.OrderTypeLimit).
		Price(sFuturesPrice).
		TimeInForce(futures.TimeInForceTypeGTC).
		ReduceOnly(true).
		Quantity(sQuantity)
	var order *futures.CreateOrderResponse
	order, err = createOrderService.Do(context.Background())

	if err == nil {
		orderId = order.OrderID
	}

	return
}

func getBinanceOrderInf(symbol string, orderId int64) (cummuQuoteQty, execQty, execPrice float64, orderStatus binance.OrderStatusType, err error) {
	getOrderService := binanceClient.NewGetOrderService().
		Symbol(symbol).
		OrderID(orderId)
	orderInf, err := getOrderService.Do(context.Background())
	Info.Println(orderInf)
	if err != nil {
		Error.Println(err)
		return
	}
	orderStatus = orderInf.Status
	if orderInf.Status != binance.OrderStatusTypePartiallyFilled && orderInf.Status != binance.OrderStatusTypeFilled {
		Error.Printf("Order %v status is %v\n", orderId, orderInf.Status)
		err = errors.New("Order not filled")
	}
	if orderInf != nil && (orderInf.Status == binance.OrderStatusTypePartiallyFilled || orderInf.Status == binance.OrderStatusTypeFilled) {
		cummuQuoteQty, _ = strconv.ParseFloat(orderInf.CummulativeQuoteQuantity, 64)
		execQty, _ = strconv.ParseFloat(orderInf.ExecutedQuantity, 64)
		execPrice = cummuQuoteQty / execQty
	}
	return
}

func getFuturesOrderInf(symbol string, orderId int64) (futuresCummuQuoteQty, futuresExecQty, futuresExecPrice float64, orderStatus futures.OrderStatusType, err error) {
	getOrderService := futuresClient.NewGetOrderService().
		Symbol(symbol).
		OrderID(orderId)
	orderInf, err := getOrderService.Do(context.Background())
	Info.Println(orderInf)
	if err != nil {
		Error.Println(err)
		return
	}
	orderStatus = orderInf.Status
	if orderInf.Status != futures.OrderStatusTypePartiallyFilled && orderInf.Status != futures.OrderStatusTypeFilled {
		Error.Printf("Order %v status is %v\n", orderId, orderInf.Status)
		err = errors.New("Order not filled")
	}
	if orderInf != nil && (orderInf.Status == futures.OrderStatusTypePartiallyFilled || orderInf.Status == futures.OrderStatusTypeFilled) {
		futuresCummuQuoteQty, _ = strconv.ParseFloat(orderInf.CumQuote, 64)
		futuresExecQty, _ = strconv.ParseFloat(orderInf.ExecutedQuantity, 64)
		futuresExecPrice = futuresCummuQuoteQty / futuresExecQty
	}
	return
}

func cancelBinanceLimitOrder(symbol string, orderId int64) error {
	cancelOrderService := binanceClient.NewCancelOrderService().
		Symbol(symbol).
		OrderID(orderId)
	order, err := cancelOrderService.Do(context.Background())
	Info.Println(order, err)
	return err
}

func cancelFuturesLimitOrder(symbol string, orderId int64) error {
	cancelOrderService := futuresClient.NewCancelOrderService().
		Symbol(symbol).
		OrderID(orderId)
	order, err := cancelOrderService.Do(context.Background())
	Info.Println(order, err)
	return err
}

func createArbitrageLimitOrder(quoteQty float64, binancePrice, futuresPrice, symbol string) (binanceOrderId, futuresOrderId int64, binanceErr, futuresErr error) {
	binanceOrderId, binanceErr = createBinanceBuyLimitOrder(symbol, binancePrice, quoteQty)
	futuresOrderId, futuresErr = createFuturesSellLimitOrder(symbol, futuresPrice, quoteQty)
	Info.Printf("CreateArbitrageLimitOrder, symbol=%v, binancePrice=%v, futuresPrice=%v, binanceOrderId=%v, futuresOrderId=%v, binanceErr=%v, futuresErr=%v\n",
		symbol, binancePrice, futuresPrice, binanceOrderId, futuresOrderId, binanceErr, futuresErr)
	// 现货单报错，合约单没报错，取消合约单
	if binanceErr != nil && futuresErr == nil {
		err := cancelFuturesLimitOrder(symbol, futuresOrderId)
		if err != nil {
			Warning.Println(err)
			futuresErr = errors.New("future order is already success")
		}
	} else if binanceErr == nil && futuresErr != nil {
		err := cancelBinanceLimitOrder(symbol, binanceOrderId)
		if err != nil {
			Warning.Println(err)
			binanceErr = errors.New("client order is already success")
		}
	}
	return
}

func closeArbitrageLimitOrder(qty, futuresQty float64, binancePrice, futuresPrice, symbol string) (binanceOrderId, futuresOrderId int64, binanceErr, futuresErr error) {
	binanceOrderId, binanceErr = createBinanceSellLimitOrder(symbol, binancePrice, qty)
	futuresOrderId, futuresErr = createFuturesBuyLimitOrder(symbol, futuresPrice, futuresQty)
	Info.Printf("CloseArbitrageLimitOrder, symbol=%v, binancePrice=%v, binanceQty=%v, futuresPrice=%v, futuresQty=%v, binanceOrderId=%v, futuresOrderId=%v, binanceErr=%v, futuresErr=%v\n",
		symbol, binancePrice, qty, futuresPrice, futuresQty, binanceOrderId, futuresOrderId, binanceErr, futuresErr)
	// 现货单报错，合约单没报错，取消合约单
	if binanceErr != nil && futuresErr == nil {
		err := cancelFuturesLimitOrder(symbol, futuresOrderId)
		if err != nil {
			Warning.Println(err)
			futuresErr = errors.New("future order is already success")
		}
	} else if binanceErr == nil && futuresErr != nil {
		err := cancelBinanceLimitOrder(symbol, binanceOrderId)
		if err != nil {
			Warning.Println(err)
			binanceErr = errors.New("client order is already success")
		}
	}
	return
}
